package org.datacoder.geocoding.jurisdiction

import grails.orm.HibernateCriteriaBuilder
import org.datacoder.util.*

class ZipCodeService {

    def jurisdictionService
    def sessionFactory
    def stateService

    static transactional = true

    /* CRUD */
    public ZipCode create(ZipCode zc) throws DAOException{
        save(zc)
        return zc
    }
    public void save(ZipCode zc) throws DAOException{
        ServiceUtils.save(zc)
    }
    public void save(ZipCode zc, boolean flush) throws DAOException{
        ServiceUtils.save(zc, flush)
    }
    public void delete(ZipCode zc){
        if(zc == null){
            return
        }
        zc.delete(flush:true)
    }
    public ZipCode merge(ZipCode zc){
        return zc.merge()
    }
    public ZipCode get(String zcId){
        return get(Long.parseLong(zcId))
    }
    public ZipCode get(long zcId){
        return ZipCode.get(zcId)
    }
    public Set<ZipCode> getAll(){
        return ZipCode.findAll()
    }
    /* END CRUD */

    public ZipCode getByNameAndStateOrCreate(String name, State state, String longName){
        ZipCode zc = getByNameAndState(name, state)
        if(zc == null){
            zc = new ZipCode(
                name:name,
                longName:longName,
                state:state
            )
            zc = create(zc)
        }
        return zc
    }

    public ZipCode getByNameAndState(String name, State state){
        ZipCode zc = ZipCode.findByNameAndState(name, state)
        return zc
    }

    public ZipCode getByName(String name) {
        return ZipCode.findByName(name)
    }

    public Map<Class, Set<Jurisdiction>> getJurisdictionsByZipCode(String zipCodeString){
        ZipCode zipCode = ZipCode.findByName(zipCodeString)
        if(zipCode == null){
            return null
        }
        return getJurisdictionsByZipCode(zipCode)
    }

    public Map<Class, Set<Jurisdiction>> getJurisdictionsByZipCode(ZipCode zip){
        Map<Class, Set<Jurisdiction>> classJurisSetMap = new HashMap<Class, Set<Jurisdiction>>()
        Set<Jurisdiction> jurisSet

        jurisSet = new HashSet<Jurisdiction>()
        jurisSet.add(zip.state)
        classJurisSetMap.put(State.class, jurisSet)

        classJurisSetMap.put(County.class, getJurisdictionsInRange(County.class, zip, 0.01))
        classJurisSetMap.put(Municipality.class, getJurisdictionsInRange(Municipality.class, zip, 0.01))
        classJurisSetMap.put(CongressionalDistrict.class, getJurisdictionsInRange(CongressionalDistrict.class, zip, 0.01))
        classJurisSetMap.put(SenatorialDistrict.class, getJurisdictionsInRange(SenatorialDistrict.class, zip, 0.01))
        classJurisSetMap.put(RepresentativeDistrict.class, getJurisdictionsInRange(RepresentativeDistrict.class, zip, 0.01))
        classJurisSetMap.put(VotingDistrict.class, getJurisdictionsInRange(VotingDistrict.class, zip, 0.01))

        return classJurisSetMap
    }

    private Set<Jurisdiction> getJurisdictionsInRange(Class clazz, ZipCode zip, double range){
        def c = [ compare: {
                a, b -> a.distance(zip).compareTo(b.distance(zip))
        } ] as Comparator

        Set<Jurisdiction> jurisSet = new TreeSet<Jurisdiction>(c)
        HibernateCriteriaBuilder criteria
        while(jurisSet.size() < 4){
            criteria = new HibernateCriteriaBuilder(clazz, sessionFactory)
            jurisSet = criteria{
                and{
                    eq("state", zip.state)
                    between("latitude", zip.latitude - range, zip.latitude + range)
                    between("longitude", zip.longitude - range, zip.longitude + range)
                }
            }
            range *= 1.5
        }
        def sorted = new TreeSet(c)
        sorted.addAll(jurisSet)
        return sorted
    }

}

